package com.aaron.gesturehelper.gesturelib;

import java.io.Closeable;
import java.io.IOException;
import android.util.Log;

import com.aaron.gesturehelper.MyConfiguration;

public class MyGestureUtil {
	
	private static final String LOG_TAG = "GestureUtil";

	private static final int SEQUENCE_SAMPLE_SIZE = 16;

    private final static float[] ORIENTATIONS = {
            0, (float) (Math.PI / 4), (float) (Math.PI / 2), (float) (Math.PI * 3 / 4),
            (float) Math.PI, -0, (float) (-Math.PI / 4), (float) (-Math.PI / 2),
            (float) (-Math.PI * 3 / 4), (float) -Math.PI
    };
    
	private MyGestureUtil() {
	}

	/**
	 * Closes the specified stream.
	 * 
	 * @param stream
	 *            The stream to close.
	 */
	static void closeStream(Closeable stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				if(MyConfiguration.DEBUG) {
					Log.e(LOG_TAG, "Could not close stream", e);
				}
			}
		}
	}

	/**
	 * Samples a stroke temporally into a given number of evenly-distributed
	 * points.
	 * 
	 * @param stroke
	 *            the gesture stroke to be sampled
	 * @param numPoints
	 *            the number of points
	 * @return the sampled points in the form of [x1, y1, x2, y2, ..., xn, yn]
	 */
	public static float[] temporalSampling(MyGestureStroke stroke, int numPoints) {
		final float increment = stroke.length / (numPoints - 1);
		int vectorLength = numPoints * 2;
		float[] vector = new float[vectorLength];
		float distanceSoFar = 0;
		float[] pts = stroke.points;
		float lstPointX = pts[0];
		float lstPointY = pts[1];
		int index = 0;
		float currentPointX = Float.MIN_VALUE;
		float currentPointY = Float.MIN_VALUE;
		vector[index] = lstPointX;
		index++;
		vector[index] = lstPointY;
		index++;
		int i = 0;
		int count = pts.length / 2;
		while (i < count) {
			if (currentPointX == Float.MIN_VALUE) {
				i++;
				if (i >= count) {
					break;
				}
				currentPointX = pts[i * 2];
				currentPointY = pts[i * 2 + 1];
			}
			float deltaX = currentPointX - lstPointX;
			float deltaY = currentPointY - lstPointY;
			float distance = (float) Math.sqrt(deltaX * deltaX + deltaY
					* deltaY);
			if (distanceSoFar + distance >= increment) {
				float ratio = (increment - distanceSoFar) / distance;
				float nx = lstPointX + ratio * deltaX;
				float ny = lstPointY + ratio * deltaY;
				vector[index] = nx;
				index++;
				vector[index] = ny;
				index++;
				lstPointX = nx;
				lstPointY = ny;
				distanceSoFar = 0;
			} else {
				lstPointX = currentPointX;
				lstPointY = currentPointY;
				currentPointX = Float.MIN_VALUE;
				currentPointY = Float.MIN_VALUE;
				distanceSoFar += distance;
			}
		}

		for (i = index; i < vectorLength; i += 2) {
			vector[i] = lstPointX;
			vector[i + 1] = lstPointY;
		}
		return vector;
	}

	/**
	 * Calculates the centroid of a set of points.
	 * 
	 * @param points
	 *            the points in the form of [x1, y1, x2, y2, ..., xn, yn]
	 * @return the centroid
	 */
	static float[] computeCentroid(float[] points) {
		float centerX = 0;
		float centerY = 0;
		int count = points.length;
		for (int i = 0; i < count; i++) {
			centerX += points[i];
			i++;
			centerY += points[i];
		}
		float[] center = new float[2];
		center[0] = 2 * centerX / count;
		center[1] = 2 * centerY / count;

		return center;
	}

	 public static float[] temporalSampler(MyGestureStroke stroke) {
	        float[] pts = MyGestureUtil.temporalSampling(stroke,
	                SEQUENCE_SAMPLE_SIZE);
	        float[] center = MyGestureUtil.computeCentroid(pts);
	        float orientation = (float)Math.atan2(pts[1] - center[1], pts[0] - center[0]);

	        float adjustment = -orientation;
	       
	        int count = ORIENTATIONS.length;
	        for (int i = 0; i < count; i++) {
	            float delta = ORIENTATIONS[i] - orientation;
	            if (Math.abs(delta) < Math.abs(adjustment)) {
	                adjustment = delta;
	            }
	        }

	        MyGestureUtil.translate(pts, -center[0], -center[1]);
	        MyGestureUtil.rotate(pts, adjustment);

	        return pts;
	    }
	 
	/**
	 * Calculates the "minimum" cosine distance between two instances.
	 * 
	 * @param vector1
	 * @param vector2
	 * @param numOrientations
	 *            the maximum number of orientation allowed
	 * @return the distance between the two instances (between 0 and Math.PI)
	 */
	static float minimumCosineDistance(float[] vector1, float[] vector2,
			int numOrientations) {
		final int len = vector1.length;
		float a = 0;
		float b = 0;
		for (int i = 0; i < len; i += 2) {
			a += vector1[i] * vector2[i] + vector1[i + 1] * vector2[i + 1];
			b += vector1[i] * vector2[i + 1] - vector1[i + 1] * vector2[i];
		}
		if (a != 0) {
			final float tan = b / a;
			final double angle = Math.atan(tan);
			if (numOrientations > 2
					&& Math.abs(angle) >= Math.PI / numOrientations) {
				return (float) Math.acos(a);
			} else {
				final double cosine = Math.cos(angle);
				final double sine = cosine * tan;
				return (float) Math.acos(a * cosine + b * sine);
			}
		} else {
			return (float) Math.PI / 2;
		}
	}

	static float[] rotate(float[] points, float angle) {
		float cos = (float) Math.cos(angle);
		float sin = (float) Math.sin(angle);
		int size = points.length;
		for (int i = 0; i < size; i += 2) {
			float x = points[i] * cos - points[i + 1] * sin;
			float y = points[i] * sin + points[i + 1] * cos;
			points[i] = x;
			points[i + 1] = y;
		}
		return points;
	}

	static float[] translate(float[] points, float dx, float dy) {
		int size = points.length;
		for (int i = 0; i < size; i += 2) {
			points[i] += dx;
			points[i + 1] += dy;
		}
		return points;
	}

	static float[] scale(float[] points, float sx, float sy) {
		int size = points.length;
		for (int i = 0; i < size; i += 2) {
			points[i] *= sx;
			points[i + 1] *= sy;
		}
		return points;
	}
}
